package me.magicall.db.dao;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import me.magicall.coll.ElementTransformerUtil.SerializableElementTransformer;
import me.magicall.db.meta.DbColumn;
import me.magicall.db.util.DbUtil;
import me.magicall.db.util.ReturnKeyAndAffect;
import me.magicall.util.kit.Kits;

/**
 * 目前的限制：
 * model类和表的key必须是单字段，数据类型只能是Integer或Long
 * 
 * @author MaGiCalL
 * @param <T>
 */
public abstract class CommonDaoTemplate<T> implements CommonDao<T> {

	//----------------------------------读方法 query methods
	@Override
	public Collection<T> fill(final Collection<T> modelOnlyWithIds) {
		if (Kits.COLL.isEmpty(modelOnlyWithIds)) {
			return modelOnlyWithIds;
		}

		final List<T> list = Kits.LIST.sort(modelOnlyWithIds);//for query fast

		final Object[] ids = new Object[list.size()];
		final Map<Object, T> idMap = new HashMap<Object, T>();
		int index = 0;
		for (final T t : list) {
			final Object id = getId(t);
			idMap.put(String.valueOf(id), t);
			ids[index++] = id;
		}

		final StringBuilder sb = DbUtil.buildSql_SelectAllFromWhereId(getTableName(list), getDbColumnNames(list), idColumnName());//
		sb.append(" in(");
		Kits.STR.join(sb, modelOnlyWithIds, ",", MODLE_TO_ID_PLACE_HOLDER);
		sb.append(")");

		return fill(sb.toString(), idMap, ids);
	}

	protected abstract Collection<T> fill(final String sql, final Map<Object, T> idMap, final Object[] ids);

	@Override
	public T fill(final T modelOnlyWithId) {
		final StringBuilder sb = DbUtil.buildSql_SelectAllFrom(getTableName(modelOnlyWithId), getDbColumnNames(modelOnlyWithId));
		appendWhereIdEqualsPlaceHolder(sb);
		return fill(modelOnlyWithId, sb.toString());
	}

	protected abstract T fill(final T modelOnlyWithId, final String sql);

//========================================= 写方法 update methods 
	//------------------------------------- 多model方法 methods about multi models
	@Override
	public int[] add(final Collection<? extends T> models) {
		if (Kits.COLL.isEmpty(models)) {
			return Kits.INT.emptyPrimitiveArray();
		}
		final StringBuilder sb = buildSql_Insert(getTableName(models), getDbColumnNames(models));
		return batchAdd(sb.toString(), models);
	}

	protected abstract int[] batchAdd(final String sql, final Collection<? extends T> models);

	@Override
	public ReturnKeyAndAffect addReturnKey(final Collection<? extends T> models) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int addIfAbsent(final Collection<? extends T> models) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int[] addOrUpdate(final Collection<? extends T> models) {
		if (Kits.COLL.isEmpty(models)) {
			return Kits.INT.emptyPrimitiveArray();
		}

		final List<String> dbColumnNames = getDbColumnNames(models);
		final StringBuilder sb = buildSql_Insert(getTableName(models), dbColumnNames).append("on duplicate key update ");
		buildSql_UpdateSetValuePart(dbColumnNames, sb);

		return batchAddOrUpdate(sb.toString(), models);
	}

	protected abstract int[] batchAddOrUpdate(final String sql, final Collection<? extends T> models);

	@Override
	public int[] update(final Collection<? extends T> models) {
		if (Kits.COLL.isEmpty(models)) {
			return Kits.INT.emptyPrimitiveArray();
		}

		final StringBuilder sb = new StringBuilder("update ").append(getTableName(models)).append(" set ");
		final List<String> dbColumnNames = getDbColumnNames(models);
		buildSql_UpdateSetValuePart(dbColumnNames, sb);
		appendWhereIdEqualsPlaceHolder(sb);

		return batchUpdate(sb.toString(), models);
	}

	protected abstract int[] batchUpdate(final String sql, final Collection<? extends T> models);

	@Override
	public int[] del(final Collection<? extends T> models) {
		if (Kits.COLL.isEmpty(models)) {
			return Kits.INT.emptyPrimitiveArray();
		}

		final StringBuilder sb = new StringBuilder("delete from ").append(getTableName(models));//
		appendWhereIdEqualsPlaceHolder(sb);

		return batchDel(sb.toString(), models);
	}

	protected abstract int[] batchDel(final String sql, final Collection<? extends T> models);

	//---------------------------------- 单model方法 single model handling method
	@Override
	public boolean add(final T model) {
		return addReturnKey(model).success();
	}

	@Override
	public ReturnKeyAndAffect addReturnKey(final T model) {
		final List<String> columnNames = getDbColumnNames(model);
		final StringBuilder sb = new StringBuilder("insert into ").append(getTableName(model)).append("(");
		Kits.STR.join(sb, columnNames, ",");
		sb.append(")values(");
		Kits.STR.join(sb, columnNames, ",", FIELD_TO_PLACE_HOLDER);
		sb.append(")");
		return add(sb.toString(), model);
	}

	protected abstract ReturnKeyAndAffect add(final String sql, final T model);

	@Override
	public int update(final T model) {
		final StringBuilder sb = new StringBuilder("update ").append(getTableName(model)).append(" set ");
		final List<String> dbColumnNames = getDbColumnNames(model);
		buildSql_UpdateSetValuePart(dbColumnNames, sb);
		appendWhereIdEqualsPlaceHolder(sb);

		return update(sb.toString(), model);
	}

	protected abstract int update(final String sql, final T model);

	@Override
	public boolean addOrUpdate(final T model) {
		final List<String> dbColumnNames = getDbColumnNames(model);
		final StringBuilder sb = buildSql_Insert(getTableName(model), dbColumnNames)//
				.append(" on duplicate key update ");
		buildSql_UpdateSetValuePart(dbColumnNames, sb);

		return addOrUpdate(sb.toString(), model);
	}

	protected abstract boolean addOrUpdate(final String sql, final T model);

	@Override
	public int del(final T model) {
		final StringBuilder sb = new StringBuilder("delete from ").append(getTableName(model));//
		appendWhereIdEqualsPlaceHolder(sb);

		return del(sb.toString(), model);
	}

	protected abstract int del(final String sql, final T model);

	//-----------------------使用其他同名方法实现的重载方法 methods ref to other method with same name 
	@Override
	public Collection<T> fill(final T... modelOnlyWithIds) {
		return fill(asList(modelOnlyWithIds));
	}

	@Override
	public int[] add(final T... models) {
		return add(asList(models));
	}

	@Override
	public ReturnKeyAndAffect addReturnKey(final T... models) {
		return addReturnKey(asList(models));
	}

	@Override
	public int addIfAbsent(final T... models) {
		return addIfAbsent(asList(models));
	}

	@Override
	public int[] addOrUpdate(final T... models) {
		return addOrUpdate(asList(models));
	}

	@Override
	public int[] update(final T... models) {
		return update(asList(models));
	}

	@Override
	public int[] del(final T... models) {
		return del(asList(models));
	}

//=============================================
	protected final Object getId(final T model) {
		return getProperty(model, idFieldName());
	}

	protected final List<T> asList(final T... models) {
		return Arrays.asList(models);
	}

	protected final Object getProperty(final T model, final DbColumn dbColumn) {
		return getProperty(model, DbUtil.dbNameToJavaName(dbColumn.getName()));
	}

	protected StringBuilder appendWhereIdEqualsPlaceHolder(final StringBuilder sb) {
		return sb.append(" where ").append(idColumnName()).append("=").append(placeHolderForId());
	}

	protected String placeHolderForField(final String fieldName) {
		return DbUtil.PLACE_HOLDER;
	}

	protected String placeHolderForId() {
		return placeHolderForField(idFieldName());
	}

	protected String placeHolderForId(final int index) {
		return placeHolderForId();
	}

	protected String idFieldName() {
		return DbUtil.COMMON_ID_FIELD_NAME;
	}

	protected String idColumnName() {
		return DbUtil.COMMON_ID_COLUMN_NAME;
	}

	protected String getTableName(final Collection<? extends T> models) {
		return getTableName(models.iterator().next());
	}

	protected List<String> getDbColumnNames(final Collection<? extends T> models) {
		return getDbColumnNames(models.iterator().next());
	}

	protected void buildSql_UpdateSetValuePart(final List<String> dbColumnNames, final StringBuilder sb) {
		DbUtil.buildSql_UpdateSetValuePart(sb, dbColumnNames, FIELD_TO_PLACE_HOLDER, true);
	}

	protected StringBuilder buildSql_Insert(final String tableName, final List<String> dbColumnNames) {
		return DbUtil.buildSql_Insert(dbColumnNames, tableName, FIELD_TO_PLACE_HOLDER);
	}

	protected final SerializableElementTransformer<T, String> MODLE_TO_ID_PLACE_HOLDER = new SerializableElementTransformer<T, String>() {
		private static final long serialVersionUID = 5362030051196982123L;

		@Override
		public String transform(final int index, final T element) {
			return placeHolderForId(index);
		}
	};
	protected final SerializableElementTransformer<String, String> FIELD_TO_PLACE_HOLDER = new SerializableElementTransformer<String, String>() {
		private static final long serialVersionUID = -4829831102981224486L;

		@Override
		public String transform(final int index, final String element) {
			return placeHolderForField(DbUtil.dbNameToJavaName(element));
		}
	};

//====================================== some methods need to implements
	protected abstract String getTableName(final T model);

	protected abstract List<String> getDbColumnNames(final T model);

	protected abstract Object getProperty(final T model, final String fieldName);

	protected abstract void setProperty(final T model, final String fieldName, final Object value);

}
